package com.logic.landseaserver.service.impl;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import com.logic.landseaserver.persistence.write.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.logic.common.exception.BusinessException;
import com.logic.common.security.SecurityUtils;
import com.logic.common.util.CurrentThread;
import com.logic.common.util.QueryUtil;
import com.logic.landseaserver.common.LandeaConstants;
import com.logic.landseaserver.common.LandeaConstants.BillType;
import com.logic.landseaserver.common.LandeaConstants.ProduceType;
import com.logic.landseaserver.common.LandseaErrorMessageEnum;
import com.logic.landseaserver.common.VariableChar;
import com.logic.landseaserver.common.config.SendMsgConfiguration;
import com.logic.landseaserver.common.enums.BTypeEnum;
import com.logic.landseaserver.common.enums.BillStatusEnum;
import com.logic.landseaserver.common.enums.BillTypeEnum;
import com.logic.landseaserver.common.enums.ContractStatusCodeEnum;
import com.logic.landseaserver.common.enums.DepositEnum;
import com.logic.landseaserver.common.enums.RefundTypeEnum;
import com.logic.landseaserver.common.enums.SystemCodeEnum;
import com.logic.landseaserver.common.exception.LandseaBusinessException;
import com.logic.landseaserver.common.exception.LandseaException;
import com.logic.landseaserver.common.exception.ResultCodes.PaymentResultCode;
import com.logic.landseaserver.common.util.Arith;
import com.logic.landseaserver.common.util.ConstantChar;
import com.logic.landseaserver.common.util.DateUtil;
import com.logic.landseaserver.common.util.LandSeaUtil;
import com.logic.landseaserver.common.util.SMSUtil;
import com.logic.landseaserver.common.util.StringTools;
import com.logic.landseaserver.domain.BalanceSheetRecord;
import com.logic.landseaserver.domain.Bill;
import com.logic.landseaserver.domain.BillDetail;
import com.logic.landseaserver.domain.BillPayment;
import com.logic.landseaserver.domain.Contract;
import com.logic.landseaserver.domain.Deposit;
import com.logic.landseaserver.domain.LateFeeDetail;
import com.logic.landseaserver.domain.MonthlyBill;
import com.logic.landseaserver.domain.PosPaymentCallback;
import com.logic.landseaserver.domain.PreOffrentBill;
import com.logic.landseaserver.domain.PreOffrentDetailBill;
import com.logic.landseaserver.domain.Project;
import com.logic.landseaserver.domain.RealBill;
import com.logic.landseaserver.domain.Refund;
import com.logic.landseaserver.domain.Room;
import com.logic.landseaserver.models.authcode.bean.AddCouponUesd;
import com.logic.landseaserver.models.authcode.bean.UsedAuthCode;
import com.logic.landseaserver.models.authcode.service.CouponUsedService;
import com.logic.landseaserver.models.independent.service.BillIndependentService;
import com.logic.landseaserver.models.latefee.service.LateFeeService;
import com.logic.landseaserver.models.payment.PaymentConstant;
import com.logic.landseaserver.models.payment.PaymentConstant.PayType;
import com.logic.landseaserver.models.prepayment.service.AutoPayBillService;
import com.logic.landseaserver.models.prepayment.service.PrepaymentService;
import com.logic.landseaserver.persistence.read.BalanceSheetRecordReadMapper;
import com.logic.landseaserver.persistence.read.BillDetailReadMapper;
import com.logic.landseaserver.persistence.read.BillPaymentReadMapper;
import com.logic.landseaserver.persistence.read.BillReadMapper;
import com.logic.landseaserver.persistence.read.ContractReadMapper;
import com.logic.landseaserver.persistence.read.DepositReadMapper;
import com.logic.landseaserver.persistence.read.MonthlyBillReadMapper;
import com.logic.landseaserver.persistence.read.ProjectReadMapper;
import com.logic.landseaserver.persistence.read.RealBillReadMapper;
import com.logic.landseaserver.persistence.read.RoomReadMapper;
import com.logic.landseaserver.service.IBillPaymentService;
import com.logic.landseaserver.service.IBillService;
import com.logic.landseaserver.service.bill.BillFactory;
import com.logic.landseaserver.service.bill.HourseBillFactory;
import com.logic.landseaserver.service.bill.PropertyBillFactory;
import com.logic.landseaserver.service.callback.IWebankCallbackHandler;
import com.logic.landseaserver.ws.dto.BillDTO;
import com.logic.landseaserver.ws.dto.BillDetailAndPayDTO;
import com.logic.landseaserver.ws.dto.BillDetailDTO;
import com.logic.landseaserver.ws.dto.BillPaymentDTO;
import com.logic.landseaserver.ws.dto.BillSelectAllDTO;
import com.logic.landseaserver.ws.dto.ContractBillDTO;
import com.logic.landseaserver.ws.dto.CurrentBillDTO;
import com.logic.landseaserver.ws.dto.DateTimeDTO;
import com.logic.landseaserver.ws.dto.DelayOffBillDTO;
import com.logic.landseaserver.ws.dto.QueryCouponUesdDTO;
import com.logic.landseaserver.ws.dto.UserBillDTO;
import com.logic.landseaserver.ws.request.AddAuthCodeReq;
import com.logic.landseaserver.ws.request.ContractNeedDelReq;
import com.logic.landseaserver.ws.request.CurrentBillReq;
import com.logic.system.AppContext;
import com.logic.system.domain.SystemFile;
import com.logic.system.service.IConfigurationService;
import com.logic.system.service.impl.ConfigurationService;
import com.logic.system.service.impl.FileService;

/**
 * @Author: jack
 * @Date: 2017/5/4
 * @Description: 账单接口实现类
 */
@Service
public class BillServiceImpl implements IBillService
{
    private static final Logger log= LoggerFactory.getLogger(BillServiceImpl.class);

    @Autowired
    private BillReadMapper billReadMapper;

    @Autowired
    private RealBillReadMapper realBillReadMapper;

    @Autowired
    private BillWriteMapper billWriteMapper;

    @Autowired
    private BillDetailReadMapper billDetailReadMapper;

    @Autowired
    private BillDetailWriteMapper billDetailWriteMapper;

    @Autowired
    private BillPaymentReadMapper billPaymentReadMapper;

    @Autowired
    private IBillPaymentService iBillPaymentService;

    @Autowired
    private ContractReadMapper contractReadMapper;

    @Autowired
    private IConfigurationService configurationService;

    @Autowired
    private FileService fileService;

    @Autowired
    private IBillPaymentService billPayService;

    @Autowired
    private DepositReadMapper depositReadMapper;

    @Autowired
    private DepositWriteMapper depositWriteMapper;

    @Autowired
    private BillPaymentWriteMapper billPaymentWriteMapper;

    @Autowired
    private MonthlyBillReadMapper monthlyBillReadMapper;

    @Autowired
    private MonthlyBillWriteMapper monthlyBillWriteMapper;

    @Autowired
    private AppContext appContext;

    @Autowired
    private RoomReadMapper roomReadMapper;

    @Autowired
    private BalanceSheetRecordReadMapper balanceSheetRecordReadMapper;

    @Autowired
    private BalanceSheetRecordWriteMapper balanceSheetRecordWriteMapper;

    @Autowired
    private BillIndependentService billIndependentService;

    @Autowired
    private SendMsgConfiguration sendMsgConfiguration;

    @Autowired
    private SendMsgServiceImpl sendMsgServiceImpl;

    @Autowired
    private CouponUsedService couponUsedService;

    @Autowired
    private AutoPayBillService autoPayBillService;

    @Autowired
    private PrepaymentService prepaymentService;

    @Autowired
    private ProjectReadMapper projectReadMapper;
    
    @Autowired
    private LateFeeService lateFeeService;

    @Autowired
    private ContractWriteMapper contractWriteMapper;

    @Override
    public List<CurrentBillDTO> getBillInfoByCurrent(CurrentBillReq req)
        throws BusinessException
    {

        if (req.getContractId() == null)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILL_INFO_CONTRACT_ID_IS_NULL);
        }

        List<CurrentBillDTO> currentBillDTOList = billReadMapper.getCurrentBill(req);

        for (CurrentBillDTO currentBillDTO : currentBillDTOList)
        {
            // 当月的账单 获取授权码使用记录
            HashMap queryMap = new HashMap();
            queryMap.put("mainId", currentBillDTO.getId());
            queryMap.put("mainType", LandeaConstants.QueryUesdCodeType.MONTHBILL);
            List<QueryCouponUesdDTO> uesdCodes = couponUsedService.querypPersonalCouponList(queryMap);
            currentBillDTO.setAuthCodes(uesdCodes);

            if (currentBillDTO != null && currentBillDTO.getBillPaymentList().size() > 0)
            {
                for (BillPaymentDTO dto : currentBillDTO.getBillPaymentList())
                {
                    if (!StringUtils.isEmpty(dto.getImageId()))
                    {
                        dto.setImagePath(fileService.getFilesByIds(dto.getImageId()));
                    }
                }
            }
        }
        return currentBillDTOList;
    }

    @Override
    public List<UserBillDTO> getBillList(CurrentBillReq req)
        throws LandseaBusinessException
    {
        if (req.getBillStatus() == null)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILL_INFO_CONTRACT_ID_IS_NULL);
        }
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        if (userId == null || userId == -1)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.TOKEN_IS_NULL);
        }
        req.setUserId(userId);
        List<UserBillDTO> resultList = new ArrayList<>();

        // 查询个人月结账单列表
        List<UserBillDTO> monthBillList = billReadMapper.queryUserBillList(req);
        // 查询个人实时月结账单列表
        List<UserBillDTO> realBillList = realBillReadMapper.queryUserRealBillList(req);
        resultList.addAll(monthBillList);
        resultList.addAll(realBillList);
        Collections.sort(resultList, new Comparator<UserBillDTO>()
        {
            public int compare(UserBillDTO o1, UserBillDTO o2)
            {
                return o2.getCreateTime().compareTo(o1.getCreateTime());
            }
        });
        return resultList;
    }

    @Override
    public CurrentBillDTO getBillDetail(Integer billId)
        throws LandseaBusinessException
    {
        CurrentBillReq req = new CurrentBillReq();
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        if (userId == null || userId == -1)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.TOKEN_IS_NULL);
        }
        req.setUserId(userId);
        req.setBillId(billId);
        List<CurrentBillDTO> currentBillDTOList = billReadMapper.getCurrentBill(req);

        if (currentBillDTOList != null && currentBillDTOList.size() > 0)
        {
            return currentBillDTOList.get(0);
        }
        return null;
    }

    @Override
    public List<CurrentBillDTO> queryBillList(CurrentBillReq req)
        throws LandseaBusinessException
    {
        if (req.getContractId() == null)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILL_INFO_CONTRACT_ID_IS_NULL);
        }
        if (req.getBillStatus() == null)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILL_INFO_BILL_STATUS_IS_NULL);
        }
        List<CurrentBillDTO> resultList = billReadMapper.queryBillList(req);

        return resultList;
    }

    @Override
    public List<BillSelectAllDTO> getBillByCurrent(HashMap pmap)
        throws BusinessException
    {
        List<BillSelectAllDTO> allList = new ArrayList<BillSelectAllDTO>();
        List<BillDTO> bList = billReadMapper.selectAllByCurrent(pmap);
        for (int i = 0; i < bList.size(); i++)
        {
            BillSelectAllDTO bsad = new BillSelectAllDTO();
            // 组装账单信息
            BillDTO bd = (BillDTO)bList.get(i);
            bsad.setContractId(bd.getContractId());
            bsad.setName(bd.getName());
            bsad.setReceivableMoney(bd.getReceivableMoney());
            bsad.setAmountReceived(bd.getAmountReceived());
            bsad.setRemark(bd.getRemark());
            bsad.setBillMonth(bd.getBillMonth());
            bsad.setCheckStatus(bd.getCheckStatus());
            bsad.setContractNo(bd.getContractNo());
            bsad.setBillNo(bd.getBillNo());
            bsad.setPrjName(bd.getPrjName());
            bsad.setContractName(bd.getContractName());
            bsad.setRoomNo(bd.getRoomNo());
            bsad.setBillStatus(bd.getBillStatus());

            allList.add(bsad);
            // 查询账单详情
            HashMap dhm = new HashMap();
            dhm.put("billId", bd.getId());
            List<BillDetailDTO> biLi = billDetailReadMapper.selectAllByCurrent(dhm);
            List<BillDetailDTO> newBiLi = new ArrayList<BillDetailDTO>();
            for (int j = 0; j < biLi.size(); j++)
            {
                BillDetailDTO bi = biLi.get(j);
                DateTimeDTO dat = new DateTimeDTO();
                dat.setDateBegin(bi.getDateBegin());
                dat.setDateEnd(bi.getDateEnd());
                bi.setBeginEndDate(null);
                newBiLi.add(bi);
            }
            bsad.setBillDetailList(newBiLi);
            // 查询支付详情
            HashMap phm = new HashMap();
            phm.put("billId", bd.getId());
            List<BillPaymentDTO> paLi = billPaymentReadMapper.selectAllByCurrent(phm);
            for (int k = 0; k < paLi.size(); k++)
            {
                BillPaymentDTO bpd = paLi.get(k);
                if (null != bpd.getImageId() && !"".equals(bpd.getImageId()))
                {
                    // 获取凭证图片路径
                    List<SystemFile> detailImgs2 = new ArrayList<>();
                    try
                    {
                        for (String imgid2 : bpd.getImageId().split(","))
                        {
                            SystemFile img2 = (SystemFile)fileService.get(imgid2);
                            detailImgs2.add(img2);
                        }
                        bpd.setImagePath(detailImgs2);
                    }
                    catch (BusinessException e)
                    {
                        log.error("getBillByCurrent error.", e);
                    }
                }
            }
            bsad.setBillPaymentList(paLi);
        }

        return allList;
    }

    /**
     * [描述]：事务已添加</br>
     *
     * @param contractId
     * @throws LandseaException
     */
    @Override
    @Transactional(propagation = Propagation.SUPPORTS, isolation = Isolation.READ_UNCOMMITTED, rollbackFor = {
        LandseaException.class, Exception.class})
    public void createFirstBill(Integer contractId, List<UsedAuthCode> authCodes)
        throws LandseaException
    {
        ContractBillDTO contract = contractReadMapper.queryContractForBill(contractId);
        // 校验合同是否合法
        if (contract != null)
        {
            if (contract.getLeaseM() == null || contract.getDepositM() == null || contract.getPayM() == null)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.BILL_LEASEINFO_IS_NULL);
            }
            // 校验是否已经存在账单
            List<Bill> bills = billReadMapper.queryBillForContract(contractId);
            if (bills.size() > 0)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.BILL_ALREADY_EXIST_BILL);
            }
            createFirstBills(contract, authCodes);
            // 需要推送消息Tony
            // sendMsgServiceImpl.sendMessage(contractId);
        }
        else
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_NOT_EXIST_CONTRACT);
        }
    }

    @Override
    public void createFirstBill4ChangeRoom(Contract contract, List<UsedAuthCode> authCodes)
        throws LandseaException
    {
        Project project = projectReadMapper.selectByPrimaryKey(contract.getProjectId());
        if (null == project)
        {
            throw LandseaException.createException(PaymentResultCode.E00150012);
        }

        // 校验是否已经存在账单
        List<Bill> bills = billReadMapper.queryBillForContract(contract.getId());
        if (bills.size() > 0)
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_ALREADY_EXIST_BILL);
        }

        ContractBillDTO contractBillDTO = contract2ContractBillDTO(contract);

        createFirstBills(contractBillDTO, authCodes);
    }


    private void createFirstBills(ContractBillDTO contract, List<UsedAuthCode> authCodes)
        throws LandseaException
    {
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        List<BillDetail> objList = new ArrayList<BillDetail>();
        Date startTime = contract.getInDate();
        // 当前月的天数
        Double dayOfMonth = 0.0;
        int dayMonth = DateUtil.getInstence().getDaysOfMouth(startTime);
        // 当月剩余天数
        int dayOfMonthHave = DateUtil.getInstence().getDaysOfMouthHave(startTime);
        // 入住日为1号 且租期满不一个月的
        // if(dayMonth==dayOfMonthHave && contract.getLeaseM()<1){
        // dayOfMonth = (double)dayMonth;
        // }else{
        dayOfMonth = configurationService.getDoubleValue(ConfigurationService.SYS_MONTHLY_AVA_DAYS);
        // }
        String billMonth = DateUtil.getInstence().convertYM(startTime);
        // 一个月房租
        Double monthHoursePrice = contract.getSalePrice();
        // 一个月物业
        Double monthPropertyPrice = contract.getPropertyFee();
        // 要支付多少月
        int payM = contract.getPayM();
        // 租期
        int leaseM = contract.getLeaseM();
        // 父账单
        Bill parBill = new Bill();
        parBill.setContractId(contract.getId());
        parBill.setName(ConstantChar.billing_of_first_total_fee);
        parBill.setBillStatus(BillStatusEnum.BIST3.getCode());
        parBill.setBillMonth(billMonth);
        parBill.setAmountReceived(0.0);
        parBill.updateCommonInfo(userId);
        billWriteMapper.insertSelective(parBill);

        // 押金
        BillDetail depositFee = new BillDetail();

        depositFee.setDateEnd(contract.getOutDate());
        depositFee.setType(BillTypeEnum.CTBI11.getCode());
        depositFee.setMoney(contract.getDeposit());
        // 水费押金
        BillDetail waterFee = new BillDetail();
        waterFee.setDateEnd(contract.getOutDate());
        waterFee.setType(BillTypeEnum.CTBI10.getCode());
        waterFee.setMoney(contract.getWaterDeposit());

        // 物业费
        BillDetail propertyFee = new BillDetail();
        propertyFee.setType(BillTypeEnum.CTBI9.getCode());
        // 房租
        BillDetail hourseFee = new BillDetail();
        hourseFee.setType(BillTypeEnum.CTBI8.getCode());

        double hoursePrice = 0.0;
        double propertyPrice = 0.0;

        // String realPayMode = LandSeaUtil.getRealPayMode(contract.getPaymentMode(), leaseM);
        // 全额付->如果付X=租期Y 或者付x>租期Y
        if (payM >= leaseM)
        {
            // 如果租期不足一个月
            if (leaseM == 0)
            {
                int contractDays = DateUtil.differentDaysByMillisecond(contract.getInDate(), contract.getOutDate());
                hoursePrice = monthHoursePrice / dayOfMonth * contractDays;
                hourseFee.setDateEnd(contract.getOutDate());

                propertyPrice = contract.getPropertyFee() / dayOfMonth * contractDays;
                propertyFee.setDateEnd(contract.getOutDate());
            }
            else
            {
                // Date hsAndPrtEndDate = DateUtils.addDays(DateUtils.addMonths(startTime, leaseM), -1);
                Double dhrsPrice = 0.0;
                Double dPrtPrice = 0.0;
                if (contract.getLeaseD() > 0)
                {
                    dhrsPrice = monthHoursePrice / dayOfMonth * contract.getLeaseD();
                    dPrtPrice = contract.getPropertyFee() / dayOfMonth * contract.getLeaseD();
                }
                hoursePrice = leaseM * monthHoursePrice + dhrsPrice;
                hourseFee.setDateEnd(contract.getOutDate());
                propertyPrice = leaseM * contract.getPropertyFee() + dPrtPrice;
                propertyFee.setDateEnd(contract.getOutDate());
            }
        }
        else
        {
            payM = payM - 1;
            Date hsAndPrtEndDate = DateUtil.getInstence().getLastDayOfMonth(DateUtils.addMonths(startTime, payM));
            double daysPrice = 0.0;
            double daypPrice = 0.0;
            // 如果入住日为1号 则默认为1个月房租
            if (dayMonth == dayOfMonthHave)
            {
                daysPrice = monthHoursePrice;
                daypPrice= monthPropertyPrice;
            }
            else
            {
                daysPrice = new BigDecimal(monthHoursePrice / dayOfMonth * dayOfMonthHave).doubleValue();
                daypPrice =  new BigDecimal(monthPropertyPrice / dayOfMonth * dayOfMonthHave).doubleValue();
            }
            double mouthPrice = new BigDecimal(monthHoursePrice * payM).doubleValue();
            hoursePrice = daysPrice + mouthPrice;
            hourseFee.setDateEnd(hsAndPrtEndDate);
            propertyFee.setDateEnd(hsAndPrtEndDate);
            // 物业
           // double dayPrt = new BigDecimal(monthPropertyPrice / dayOfMonth * dayOfMonthHave).doubleValue();
            propertyPrice = daypPrice + (payM * monthPropertyPrice);
        }
        hourseFee.setMoney(LandSeaUtil.getIntDouble(hoursePrice));
        propertyFee.setMoney(LandSeaUtil.getIntDouble(propertyPrice));

        objList.add(depositFee);
        objList.add(hourseFee);
        objList.add(propertyFee);
        objList.add(waterFee);
        // 授权码优惠减免
        addAuthCode(authCodes, objList);

        // 判断是否使用授权码
        if (!CollectionUtils.isEmpty(authCodes))
        {
            AddCouponUesd couponUesds = new AddCouponUesd();
            couponUesds.setAuthCodes(authCodes);
            couponUesds.setMainType(LandeaConstants.QueryUesdCodeType.CONTRACT);
            couponUesds.setMainId(contract.getId());
            couponUesds.setPrjId(contract.getProjectId());
            couponUesds.setRoomId(contract.getRoomId());
            couponUesds.setUserId(contract.getUserId());
            couponUsedService.insertUesdList(couponUesds);
        }
        Double totalPrice = 0.0;
        Room room = roomReadMapper.selectByPrimaryKey(contract.getRoomId());
        for (BillDetail billTail : objList)
        {
            totalPrice = totalPrice + billTail.getMoney();
            billTail.setBillId(parBill.getId());
            billTail.setDateBegin(startTime);
            if (billTail.getCouponFlag() == null)
            {
                billTail.setCouponFlag(false);
            }
            billTail.setOverlistFlag(0);
            billTail.updateCommonInfo(userId);

            // 入库独立账单表
            BalanceSheetRecord balanceSheetRecord = buildFirstBillBalanceRecord(contract, parBill, billTail);
            balanceSheetRecord.setHouseNum(room.getHouseNum());
            balanceSheetRecord.setStartTime(startTime);
            billIndependentService.insertBalanceSheet(balanceSheetRecord);
        }

        billDetailWriteMapper.insertList(objList);

        parBill.setReceivableMoney(new BigDecimal(totalPrice).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
        parBill.setAmountReceived(0.0);
        parBill.setBillMoney(new BigDecimal(totalPrice).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());

        billWriteMapper.updateByPrimaryKey(parBill);
        // 如果有定金，则需要将定金进行支付 ，支付金额进行减少
        if (contract.getDepositId() != null)
        {
            Deposit dsInfo = depositReadMapper.selectByPrimaryKey(contract.getDepositId());
            if (dsInfo != null)
            {
              /*  if (dsInfo.getStatus().equals(DepositEnum._DS2.getCode())
                    || (dsInfo.getStatus().equals(DepositEnum._DS4.getCode())
                        && contract.getStatus().equals(ContractStatusCodeEnum.NEW.getCode())))
                {*/
                    BillPaymentDTO paymentDTO = new BillPaymentDTO();
                    paymentDTO.setBillId(parBill.getId());
                    paymentDTO.setPayType(dsInfo.getPayMethod());
                    paymentDTO.setTime(dsInfo.getPayDate());
                    paymentDTO.setMoney(dsInfo.getDepositAmount());
                    paymentDTO.setTransactionNo(dsInfo.getPaySn());
                    paymentDTO.setComments(ConstantChar.deposit_bill_payment);
                    paymentDTO.setImageId(dsInfo.getImageId());
                    paymentDTO.setBank(dsInfo.getBank());
                    // 首付款
                    paymentDTO.setBillType(BTypeEnum._BT1.getCode());
                    paymentDTO.setStatus(SystemCodeEnum.PAY_TRUE.getCode());
                    // 是否要校验金额，由于账单还未生成（因为事务），所以不需要校验金额
                    paymentDTO.setNeedCheckMoney(false);

                    billPayService.insertBillPaymentDTO(paymentDTO);
                    parBill.setAmountReceived(dsInfo.getDepositAmount());
                    parBill.setBillStatus(BillStatusEnum.BIST1.getCode());
                    billWriteMapper.updateByPrimaryKey(parBill);
                    if (dsInfo.getStatus().equals(DepositEnum._DS2.getCode())
                            || (dsInfo.getStatus().equals(DepositEnum._DS4.getCode()))) {
                        // 将定金改为已使用
                        dsInfo.setStatus(DepositEnum._DS3.getCode());
                        dsInfo.updateCommonInfo(userId);
                        depositWriteMapper.updateByPrimaryKeySelective(dsInfo);
                    }
               // }
                    
                    // 预定转签约成功，添加一条 定金转押金   -1000  的记录，用来独立账单财务平帐
                    // 备份到balace_sheet中的数据
                    BalanceSheetRecord depositRecord = new BalanceSheetRecord();
                    depositRecord.setBillId(dsInfo.getId());
                    depositRecord.setProjectId(dsInfo.getPrjId());
                    depositRecord.setRoomId(dsInfo.getRoomId());
                    depositRecord.setBillType(BillType.RESERVE_BILL);
                    // 预定合同ID就是自己的ID
                    depositRecord.setContractId(dsInfo.getId());
                    Room room1 = roomReadMapper.selectByPrimaryKey(dsInfo.getRoomId());
                    depositRecord.setHouseNum(room1.getHouseNum());
                    Date currentDate = new Date();
                    depositRecord.setStartTime(currentDate);
                    depositRecord.setEndTime(dsInfo.getAvailableDate());
                    depositRecord.setReceivableMoney(-dsInfo.getDepositAmount());
                    depositRecord.setBillStatus(BillStatusEnum.BIST2.getCode());
                    depositRecord.setExpenditureCode(BillTypeEnum.CTBI18.getCode());
                    depositRecord.setExpenditure(BillTypeEnum.CTBI18.getName());
                    depositRecord.setProduceType(ProduceType.PAY);
                    depositRecord.setPayTime(currentDate);
                    depositRecord.setContractNo(dsInfo.getDepositSn());
                    depositRecord.setAmountReceived(-dsInfo.getDepositAmount());
                    billIndependentService.insertBalanceSheet(depositRecord);
            }
        }
    }

    // 首期账单时构造独立账单
    private BalanceSheetRecord buildFirstBillBalanceRecord(ContractBillDTO contract, Bill parBill, BillDetail billTail)
    {
        // 往balanceSheet中加备份
        BalanceSheetRecord record = new BalanceSheetRecord();
        record.setContractNo(contract.getContractNo());
        record.setProjectId(contract.getProjectId());
        record.setRoomId(contract.getRoomId());
        record.setContractId(contract.getId());

        record.setBillId(parBill.getId());
        record.setBillMonth(parBill.getBillMonth());
        record.setBillName(parBill.getName());
        record.setAmountReceived(0.00);
        record.setBillStatus(BillStatusEnum.BIST3.getCode());
        record.setBillType(LandeaConstants.BillType.FIRST_BILL);

        record.setExpenditureCode(billTail.getType());
        record.setReceivableMoney(billTail.getMoney());
        record.setEndTime(billTail.getDateEnd());
        record.setMark(billTail.getRemark());

        // 如果是授权码
        String expenditure = balanceSheetRecordReadMapper.selectExpenditure(billTail.getType());
        if (null != billTail.getCouponFlag() && billTail.getCouponFlag().booleanValue())
        {
            record.setExpenditure(billIndependentService.getAuthCodeBillType(billTail.getType(), expenditure));
            record.setProduceType(ProduceType.AUTHCODE);

        }
        else
        {
            record.setExpenditure(expenditure);
            record.setProduceType(ProduceType.PAY);
        }

        return record;
    }

    private void addAuthCode(List<UsedAuthCode> authCodes, List<BillDetail> objList)
    {
        // 将原始费项对应的价格存入map中
        HashMap<String, Double> feeAmoutMap = new HashMap<String, Double>();
        HashMap<String, String> feeDateMap = new HashMap<String, String>();
        objList.forEach(e -> {
            feeAmoutMap.put(e.getType(), e.getMoney());
            feeDateMap.put(e.getType(),
                DateUtil.getInstence().convertDate(e.getDateBegin()) + "|"
                    + DateUtil.getInstence().convertDate(e.getDateEnd()));
        });
        // 如果有使用授权码 则要使用优惠劵 首期只使用单次优惠的
        if (authCodes != null)
        {
            // 获取单次折扣优惠的授权码 押金减免不计算在首期账单
            List<UsedAuthCode> firstDisctCodes =
                authCodes.stream()
                    .filter(e -> e.getOfferType().equals("BS")
                        && e.getMoneyType().equals(LandeaConstants.MoneyType.DISCOUNT)
                        && !e.getAuthFee().equals(BillTypeEnum.CTBI11.getCode())
                        && !e.getAuthFee().equals(BillTypeEnum.CTBI10.getCode()))
                    .collect(Collectors.toList());
            if (firstDisctCodes != null)
            {
                for (UsedAuthCode usedCode : firstDisctCodes)
                {
                    BillDetail detailBill = new BillDetail();
                    detailBill.setType(usedCode.getAuthFee());
                    Optional<BillDetail> dBill =
                        objList.stream().filter(e -> e.getType().equals(usedCode.getAuthFee())).findFirst();
                    // 如果是折扣 则取相同费项金额进行计算
                    detailBill.setMoney(-(LandSeaUtil
                        .get2Double(dBill.get().getMoney() - (dBill.get().getMoney() * usedCode.getQuota()))));
                    if (dBill.isPresent())
                    {
                        detailBill.setDateEnd(dBill.get().getDateEnd());
                    }
                    detailBill.setCouponFlag(true);
                    detailBill.setCouponId(usedCode.getId());
                    detailBill.setAuthCode(usedCode.getAuthCode());
                    detailBill.updateCommonInfo(SecurityUtils.getCurrentLogin().getUserId());
                    objList.add(detailBill);
                }
            }
            // 金额减免的 按价格排序 优先使用价格高的
            // List<UsedAuthCode> firstMoneyCodes =
            // authCodes.stream().sorted(Comparator.comparing(UsedAuthCode::getQuota).reversed()).filter(e ->
            // e.getOfferType().equals("BS")
            // && e.getMoneyType().equals(LandeaConstants.MoneyType.MONEY)).collect(Collectors.toList());
            List<UsedAuthCode> firstMoneyCodes = authCodes.stream()
                .filter(e -> e.getOfferType().equals("BS") && e.getMoneyType().equals(LandeaConstants.MoneyType.MONEY)
                    && !e.getAuthFee().equals(BillTypeEnum.CTBI11.getCode())
                    && !e.getAuthFee().equals(BillTypeEnum.CTBI10.getCode()))
                .collect(Collectors.toList());
            if (firstMoneyCodes != null)
            {
                // 获取一共有几个费项的授权码
                Set<String> uaType = new HashSet<String>();
                for (UsedAuthCode uaCode : firstMoneyCodes)
                {
                    uaType.add(uaCode.getAuthFee());
                }
                HashMap<String, Double> saveAuthMoney = new HashMap<String, Double>();// 共优惠了多少钱
                for (String typeName : uaType)
                {
                    // 获取当前类型的费项金额
                    Double feeAmout = (Double)feeAmoutMap.get(typeName);
                    if (feeAmout == null)
                    {
                        log.info("typeName is not in first bill");
                        continue;
                    }
                    // 获取当前费项的授权码信息
                    List<UsedAuthCode> feeACodes = firstMoneyCodes.stream()
                        .sorted(Comparator.comparing(UsedAuthCode::getQuota).reversed())
                        .filter(e -> e.getAuthFee().equals(typeName))
                        .collect(Collectors.toList());
                    if (feeACodes != null)
                    {
                        for (UsedAuthCode code : feeACodes)
                        {
                            // 已优惠的金额
                            Double authMoney =
                                (Double)saveAuthMoney.get(typeName) == null ? 0.0 : (Double)saveAuthMoney.get(typeName);
                            Double uesdMoney = LandSeaUtil.get2Double(feeAmout - authMoney);// 此次最多允许使用的优惠金额
                            Double crtAuthMoney = code.getQuota();// 此次优惠金额
                            if (authMoney.compareTo(feeAmout) < 0)
                            {// 如果优惠金额<费项金额 才允许使用授权码
                                Double realAuthMoney = 0.0;
                                if (crtAuthMoney.compareTo(uesdMoney) >= 0)
                                {// 如果当前优惠金额大于等于费项金额允许使用此次最大优惠金额
                                    realAuthMoney = uesdMoney;
                                    code.setQuota(realAuthMoney);
                                }
                                else
                                {
                                    realAuthMoney = crtAuthMoney;
                                }
                                BillDetail detailBill = new BillDetail();
                                detailBill.setType(typeName);
                                String dateStr = (String)feeDateMap.get(typeName);
                                if (!StringUtils.isEmpty(dateStr))
                                {
                                    detailBill.setDateEnd(DateUtil.getInstence().convertDate(dateStr.split("\\|")[1]));
                                }
                                detailBill.setMoney(-realAuthMoney);
                                detailBill.setCouponFlag(true);
                                detailBill.setCouponId(code.getId());
                                detailBill.setAuthCode(code.getAuthCode());
                                detailBill.updateCommonInfo(SecurityUtils.getCurrentLogin().getUserId());
                                objList.add(detailBill);
                                saveAuthMoney.put(typeName, realAuthMoney);
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public Boolean getBillStatusForId(Integer contractId)
        throws BusinessException
    {
        boolean flag = true;
        // 校验是否已经存在账单
        List<Bill> bills = billReadMapper.queryBillForContract(contractId);
        if (bills.size() > 0)
        {
            // 部分支付 BIST1 已支付 BIST2 未支付 BIST3
            for (int i = 0; i < bills.size(); i++)
            {
                Bill bi = bills.get(i);
                String status = bi.getBillStatus();
                if ("BIST3".equals(status))
                {
                    flag = false;
                }
            }
        }
        else
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILL_NOT_EXIST_CONTRACT);
        }
        return flag;
    }

    @Override
    public Boolean getBillStatusForContractId(Integer contractId)
        throws BusinessException
    {
        boolean flag = false;
        // 校验是否已经存在账单
        List<Bill> bills = billReadMapper.queryBillForContract(contractId);
        if (bills.size() > 0)
        {
            // 部分支付 BIST1 已支付 BIST2 未支付 BIST3
            for (int i = 0; i < bills.size(); i++)
            {
                Bill bi = bills.get(i);
                String status = bi.getBillStatus();
                if ("BIST2".equals(status))
                {
                    flag = true;
                }
            }
        }
        else
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILL_NOT_EXIST_CONTRACT);
        }
        return flag;
    }

    /**
     * [描述]：事务已添加</br>
     *
     * @param billId
     * @return
     * @throws BusinessException
     */
    @Override
    @Transactional(rollbackFor = {BusinessException.class, Exception.class})
    public Bill changeBillStatus(Integer billId, Double currentPayMoney)
        throws BusinessException
    {
        String billStatus = org.apache.commons.lang3.StringUtils.EMPTY;

        // 获取账单信息
        BillDTO bdto = billReadMapper.selectByPrimaryKey(billId);

        // 应收金额
        Double receivable = bdto.getReceivableMoney();

        // 已收金额
        Double amountReceived = bdto.getAmountReceived();

        // 判断 应收金额 和 已收金额 + 当前支付金额 大小

        // 部分支付 BIST1 已支付 BIST2 未支付 BIST3

        Double sumPayMoney = Arith.add(amountReceived, currentPayMoney);

        // 未支付
        if (sumPayMoney.doubleValue() == 0.00d)
        {
            billStatus = BillStatusEnum.BIST3.getCode();
        }
        // 应收金额 <= 已收金额 + 当前支付金额
        else if (receivable.doubleValue() > sumPayMoney.doubleValue())
        {
            billStatus = BillStatusEnum.BIST1.getCode();
        }
        else if (receivable.doubleValue() == sumPayMoney.doubleValue())
        {
            billStatus = BillStatusEnum.BIST2.getCode();
        }
        else
        {
            log.error("changeBillStatus|总支付金额不能大于应收金额|应收金额：" + receivable + ",已收金额：" + amountReceived + ",本次支付金额："
                + currentPayMoney);
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILLPAYMENT_PAY_MONEY_ERROR);
        }
        Bill bill = new Bill();
        setBillByBillDTO(bill, bdto);
        bill.setBillStatus(billStatus);
        bill.setAmountReceived(amountReceived + currentPayMoney);
        // 此处的创建时间一定不能改，bill表中的createedDtm计算滞纳金要用
        bill.setCreatedDtm(null);
        bill.setCreatedBy(null);
        billWriteMapper.updateByPrimaryKeySelective(bill);

        // 修改pos端对应payment表的状态
        BillPayment bpay = new BillPayment();
        bpay.setStatus("BPST1");// 已支付
        bpay.setBillId(billId);
        bpay.setPayType(PayType.POS);// POS
        billPaymentWriteMapper.updateByPaytype(bpay);

        return bill;

    }

    /**
     * modify by Aaron 2017-07-09 [描述]：事务已添加</br>
     *
     * @param posPaymentCallback
     * @throws LandseaException
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {LandseaException.class, Exception.class})
    public void posPayCallBackHandle(PosPaymentCallback posPaymentCallback)
        throws LandseaException
    {
        try
        {
            BillPayment billPayment = billPaymentReadMapper.selectByPrimaryKey(posPaymentCallback.getPaymentId());
            if (null == billPayment)
            {
                LandseaException.createException(LandseaErrorMessageEnum.PAYMENT_NOT_EXISTS_NULL);
            }
            Date payTime = new Date();
            billPayment.setTime(payTime);
            billPayment.setStatus(PaymentConstant.BillPaymentStatus.SUCCESS);
            billPayment.setPayType(PayType.POS);
            billPayment.updateCommonInfo(SecurityUtils.getCurrentLogin().getUserId());
            // 更新账单状态
            iBillPaymentService.updateBillpayment(billPayment);

            log.info("posPayCallBackHandle|updateBillpayment finished.BillId=" + posPaymentCallback.getBillId()
                + "|contractId=" + posPaymentCallback.getContractId());

            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("billId", posPaymentCallback.getBillId());
            map.put("contractId", posPaymentCallback.getContractId());
            map.put("currentPayMoney", billPayment.getMoney());
            map.put("payTime", payTime);
            String handlerName = "WebankCallBack" + posPaymentCallback.getBillType();
            Object handerObject = appContext.getBean(handlerName);
            if (null == handerObject)
            {
                log.error("posPayCallBackHandle|event is wrong|handlerName=" + handlerName);
                throw LandseaException.createException(LandseaErrorMessageEnum.WEBANK_CALLBACK_BILLTYPE_ERROR);
            }

            IWebankCallbackHandler handler = (IWebankCallbackHandler)handerObject;
            // 开始处理
            handler.process(map);

            log.info("posPayCallBackHandle|handler is finished.");
        }
        catch (LandseaException e)
        {
            throw e;
        }
        catch (Exception e)
        {
            log.error("posPayCallBackHandle|账单回调处理失败.", e);
            throw LandseaException.createException(LandseaErrorMessageEnum.WEBANK_CALLBACK_BILL_ERROR, e);
        }
    }

    @Override
    public void createLateFee()
    {

        // 查看未缴费的期账单
        CurrentBillReq params = new CurrentBillReq();
        params.setBillStatus(1);
        params.setBillName(VariableChar.bill_name_month);
        List<CurrentBillDTO> billList = billReadMapper.queryBillList(params);
        // 收取滞纳金时间
        //Integer days = configurationService.getIntegerValue("SYS_TIM_FOR_LAT_FEE");
        //if (days == null || days <= 0)
        //{
        //    return;
        //}

        // 收取滞纳金额百分比
        Double persentage = configurationService.getDoubleValue("SYS_PRO_OF_OVE_FIN");
        if (persentage == null || persentage <= 0)
        {
            return;
        }
        BillDetail billDetailPar = new BillDetail();
        for (CurrentBillDTO billDto : billList)
        {
            try
            {
                // 从合同获取计算滞纳金的时间
                Integer lateFeeDay = billDto.getLatefeeDay();
                if (null == lateFeeDay)
                {
                    continue;
                }
                
                Date freeDate = DateUtils.addDays(billDto.getCreateDtm(), lateFeeDay);
                // 如果没有到缴纳金日期，则跳出
                if (DateUtil.compareTime(freeDate, new Date()) > 0)
                {
                    continue;
                }

                log.info("开始处理滞纳金BillId=" + billDto.getId());
                
                // 处理滞纳金
                LateFeeDetail lateFeeDetail = lateFeeService.prcessLateFee(billDto, freeDate, persentage);
                if (null == lateFeeDetail)
                {
                    continue;
                }
                // 滞纳金
                double lateFee = lateFeeDetail.getLateFeeMoney();
                
                billDetailPar.setBillId(billDto.getId());
                billDetailPar.setType(BillTypeEnum.CTBI13.getCode());
                BillDetail result = billDetailReadMapper.getBillDetailByType(billDetailPar);

                Date dateBeginBalance = new Date();
                Date dateEndBalance = new Date();

                // 如果存在滞纳金账单项，则修改金额，否则增加
                String billNo = "";
                if (result != null)
                {
                    // 每天累加的
                    lateFee = Arith.add(lateFee, result.getMoney().doubleValue());
                    
                    Date endDate = new Date();
                    // 修改滞纳金账单项
                    result.setDateEnd(endDate);
                    result.setMoney(lateFee);
                    result.updateCommonInfo(-1);
                    billDetailWriteMapper.updateByPrimaryKeySelective(result);
                    dateBeginBalance = result.getDateBegin();
                    dateEndBalance = endDate;
                    billNo = result.getDetailNo();
                    lateFeeDetail.setBillDetailId(result.getId());
                }
                else
                {
                    dateBeginBalance = DateUtils.addDays(billDto.getCreateDtm(), lateFeeDay);
                    dateEndBalance = new Date();

                    result = new BillDetail();
                    // 修改滞纳金账单项
                    result.setDateBegin(dateBeginBalance);
                    result.setBillId(billDto.getId());
                    Integer maxCode = billDetailReadMapper.getMaxCodeById(billDto.getId());
                    billNo = billDto.getBillNo() + String.format("%02d", maxCode);
                    result.setDetailNo(billNo);
                    result.setType(BillTypeEnum.CTBI13.getCode());
                    result.setDateEnd(dateEndBalance);
                    result.setMoney(lateFee);
                    result.updateCommonInfo(-1);
                    billDetailWriteMapper.insertSelective(result);
                    
                    lateFeeDetail.setBillDetailId(result.getId());

                }
                
                // 入库滞纳金详情表
                lateFeeService.saveLateFeeDetail(lateFeeDetail);
                
                // 修改账单总额
                billWriteMapper.udpateReceivableMoney(billDto.getId());

                //
                MonthlyBill monthlyBill = new MonthlyBill();
                monthlyBill.setContractId(billDto.getContractId());
                monthlyBill.setBillMonth(getLastMonth(billDto.getBillMonth()));
                monthlyBill.setExpenseType(BillTypeEnum.CTBI13.getCode());
                MonthlyBill resultBill = monthlyBillReadMapper.selectForLateFee(monthlyBill);

                if (resultBill == null)
                {
                    monthlyBill.setRoomId(billDto.getRoomId());
                    monthlyBill.setBillPrice(lateFee);
                    monthlyBill.setStatus(BillStatusEnum.BIST3.getCode());
                    monthlyBill.setExpenseType(BillTypeEnum.CTBI13.getCode());
                    monthlyBill.setStartTime(DateUtils.addDays(billDto.getCreateDtm(), lateFeeDay));
                    monthlyBill.setEndTime(new Date());
                    monthlyBill.setInputType(2);
                    monthlyBill.updateCommonInfo(-1);
                    monthlyBillWriteMapper.insertSelective(monthlyBill);
                }
                else
                {
                    resultBill.setRoomId(billDto.getRoomId());
                    resultBill.setEndTime(new Date());
                    resultBill.setBillPrice(lateFee);
                    resultBill.updateCommonInfo(-1);
                    monthlyBillWriteMapper.updateByPrimaryKeySelective(resultBill);
                }
                log.info("订单编号为:" + billDto.getId() + "开始修改或更新滞纳金的独立账单");
                // 修改或更新滞纳金的独立账单
                billIndependentService.lateFeeUpdateBalanceSheetRent(billDto,
                    lateFee,
                    dateBeginBalance,
                    dateEndBalance,billNo);
            }
            catch (Exception e)
            {
                log.error("订单编号为:" + billDto.getId() + "账单月为" + billDto.getBillMonth() + "生成滞纳金异常:", e);
            }
        }
    }

    private String getLastMonth(String date)
    {
        if (StringUtils.isEmpty(date))
        {
            return null;
        }
        Date month = DateUtil.getInstence().getLastDayOfLastMonth(DateUtil.getInstence().convertYM(date));
        return DateUtil.getInstence().convertYM(month);
    }

    public void setBillByBillDTO(Bill bill, BillDTO billDTO)
    {
        if (!StringUtils.isEmpty(billDTO.getId()))
        {
            bill.setId(billDTO.getId());
        }
        if (!StringUtils.isEmpty(billDTO.getContractId()))
        {
            bill.setContractId(billDTO.getContractId());
        }
        if (!StringUtils.isEmpty(billDTO.getName()))
        {
            bill.setName(billDTO.getName());
        }
        if (!StringUtils.isEmpty(billDTO.getReceivableMoney()))
        {
            bill.setReceivableMoney(billDTO.getReceivableMoney());
        }
        if (!StringUtils.isEmpty(billDTO.getAmountReceived()))
        {
            bill.setAmountReceived(billDTO.getAmountReceived());
        }
        if (!StringUtils.isEmpty(billDTO.getRemark()))
        {
            bill.setRemark(billDTO.getRemark());
        }
        if (!StringUtils.isEmpty(billDTO.getBillStatus()))
        {
            bill.setBillStatus(billDTO.getBillStatus());
        }
        if (!StringUtils.isEmpty(billDTO.getBillMonth()))
        {
            bill.setBillMonth(billDTO.getBillMonth());
        }
        if (!StringUtils.isEmpty(billDTO.getCheckStatus()))
        {
            bill.setCheckStatus(billDTO.getCheckStatus());
        }
    }

    @Override
    public void delete(String arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub

    }

    @Override
    public Object get(Integer arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int insert(Object arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public List<?> query(QueryUtil arg0)
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void update(Object arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub

    }

    @Override
    public Boolean validate(Object arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Boolean validateForDelete(Object arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Boolean validateForInsert(Object arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Boolean validateForUpdate(Object arg0)
        throws BusinessException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void createMonthBill()
        throws LandseaException
    {
        String billMonth = DateUtil.getInstence().convertYM(new Date());
        List<ContractBillDTO> contractList = contractReadMapper.queryForCreateBill(billMonth);
        for (ContractBillDTO contract : contractList)
        {
            if (contract.getLeaseM() == null || contract.getDepositM() == null || contract.getPayM() == null)
            {
                log.info("contract id" + contract.getId() + " createMonthBill failed "
                    + LandseaErrorMessageEnum.BILL_LEASEINFO_IS_NULL.getDesc());
                continue;
            }
            String endMonth =DateUtil.getInstence().convertYM(contract.getOutDate());
            //续租
            String rentType = contract.getRentStatus()==null ? "": contract.getRentType();
            //如果是续租A合同 最后则不在出账单
            if(contract.getOriginalCId()==null && rentType.equals(LandeaConstants.ReRentType.RERENT) &&  endMonth.equals(billMonth)){
                log.info("contract id" + contract.getId() + " contractA no createBill");
                continue;
            }
            try{
                createMonthBills(contract, billMonth);
                // 余额自动抵扣账单,签字的合同才会自动抵扣
                if (ContractStatusCodeEnum.SIGN.getCode().equals(contract.getStatus())){
                    autoPayBillService.autoPay4CreateMonthBill(contract.getUserId(), contract.getRoomId());
                }
            }catch (Exception e){
                log.info("create bill error ,contract id is " + contract.getId());
                log.error("create bill error msg is : "+ e.getMessage());
            }

        }
    }

    private Integer createMonthBills(ContractBillDTO contract, String billMonth)
        throws LandseaException
    {
        List<BalanceSheetRecord> records = new ArrayList<BalanceSheetRecord>();
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        // 该期是第几期账单
        int nowTerm = DateUtil.getApartMonth(contract.getInDate(), new Date()) + 1;
        // 租期
        int leaseM = contract.getLeaseM();
        Date startTime = contract.getInDate();
        // 要支付多少月
        int payM = contract.getPayM();
        String billName = StringTools.buildBillName(ConstantChar.billing_of_term_fee, String.valueOf(nowTerm));
        // 父账单
        String billNo =
            contract.getContractNo() + new DecimalFormat("00").format(billReadMapper.getMaxBillCode(contract.getId()));
        Bill parBill = new Bill();
        parBill.setContractId(contract.getId());
        parBill.setBillNo(billNo);
        parBill.setName(billName);
        parBill.setBillStatus(BillStatusEnum.BIST3.getCode());
        parBill.setBillMonth(billMonth);
        parBill.setAmountReceived(0.0);
        parBill.updateCommonInfo(userId);
        billWriteMapper.insertSelective(parBill);

        List<BillFactory> billFactories = new ArrayList<BillFactory>();
        boolean billChangeFlag = false;

        // 不为全额付 则计算当期是否要缴房租和物业
        if (payM < leaseM)
        {
            if(contract.getLeaseD()>0){
                leaseM = leaseM+1;
            }
            Integer payMonth = LandSeaUtil.getPayMonth(nowTerm, payM, leaseM);
            log.info("create Month bill payMonth is :"+payMonth);
            Double dayOfMonth =
                Double.valueOf(configurationService.getStringValue(ConfigurationService.SYS_MONTHLY_AVA_DAYS));
            //如果为最后一期且为拆分账单，则补交剩余房租和服务费
            if(leaseM+1==nowTerm && contract.getBillChange()){
                billChangeFlag = true;
            }
            // 当期是否要缴房租和物业
            if (payMonth > 0 || billChangeFlag)
            {
                billFactories.add(new HourseBillFactory(contract, payM, nowTerm, dayOfMonth,billChangeFlag));
                billFactories.add(new PropertyBillFactory(contract, payM, nowTerm, dayOfMonth,billChangeFlag));
            }
        }
        Integer detailNum = 0;
        List<BillDetail> objList = new ArrayList<BillDetail>();
        if (!CollectionUtils.isEmpty(billFactories))
        {
            for (BillFactory factory : billFactories)
            {
                BillDetail billDts = factory.createBill();
                detailNum++;
                billDts.setDetailNo(billNo + String.format("%02d", detailNum));
                objList.add(billDts);
            }
        }
        String lastBillMonth = DateUtil.getInstence().convertYM(DateUtils.addMonths(new Date(), -1));

        List<MonthlyBill> monthlyBills = new ArrayList<>();
        // 获取其他款项
        List<MonthlyBill> mbills = monthlyBillReadMapper.selectForBill(contract.getId(), lastBillMonth);
        if (mbills.size() > 0)
        {
            monthlyBills.addAll(mbills);
        }
        // 第二期账单特殊处理
        /*
         * if(nowTerm==2 && contract.getOriginalCId()!=null &&
         * contract.getRentType().equals(LandeaConstants.ReRentType.RERENT)){ //获取续租合同的其他费项 List<MonthlyBill>
         * lastContractMBills = monthlyBillReadMapper.selectForBill(contract.getOriginalCId(), null);
         * if(lastContractMBills.size()>0){ monthlyBills.addAll(lastContractMBills); } }
         */
        if (monthlyBills.size() > 0)
        {
            for (MonthlyBill monthlyBill : monthlyBills)
            {
                // 排除滞纳金
                if (!monthlyBill.getExpenseType().equals(BillTypeEnum.CTBI13.getCode()))
                {
                    detailNum++;
                    BillDetail billDetail = new BillDetail();
                    billDetail.setType(monthlyBill.getExpenseType());
                    billDetail.setDateBegin(monthlyBill.getStartTime());
                    billDetail.setDateEnd(monthlyBill.getEndTime());
                    billDetail.setMoney(monthlyBill.getBillPrice());
                    billDetail.setDetailNo(billNo + String.format("%02d", detailNum));
                    billDetail.setConsumption(monthlyBill.getConsumption());
                    objList.add(billDetail);
                }
            }
        }
        Double totalPrice = 0.0;
        Room room = roomReadMapper.selectByPrimaryKey(contract.getRoomId());
        for (BillDetail billTail : objList)
        {
            totalPrice = totalPrice + billTail.getMoney();
            billTail.setBillId(parBill.getId());
            billTail.setRemark(null);
            billTail.setCouponFlag(false);
            billTail.setOverlistFlag(0);
            billTail.updateCommonInfo(userId);

            // 插入独立账单
            BalanceSheetRecord balanceSheetRecord =
                buildMonthBillBalanceRecord(contract, billTail, room.getHouseNum(), billMonth, billName,parBill.getId());
            billIndependentService.insertBalanceSheet(balanceSheetRecord);
        }
        if (objList.size() > 0)
        {
            billDetailWriteMapper.insertList(objList);
        }
        totalPrice = LandSeaUtil.get2Double(totalPrice);
        parBill.setReceivableMoney(totalPrice);
        parBill.setAmountReceived(0.0);
        parBill.setBillMoney(totalPrice);
        if (totalPrice == 0.0)
        {
            log.info("total price is :0.0");
            parBill.setBillStatus(BillStatusEnum.BIST2.getCode());
            
            /*
             * mod by aaron 2017-09-06 start 按照最新的规则，密码有效期跟房租有关，以前的逻辑注释掉
             */
            // 重置门锁密码的有效期
            // ddLockService.resetPasswordEffectiveTime(contract.getRoomId(), contract.getId());
            // mod by aaron 2017-09-06 end
        }

        billWriteMapper.updateByPrimaryKey(parBill);
        return parBill.getId();
    }

    // 构造月结账单的独立账单
    private BalanceSheetRecord buildMonthBillBalanceRecord(ContractBillDTO contract, BillDetail billTail,
        String houseNum, String billMonth,String billName,Integer billId)
    {
        // 月结账单往balanceSheet中备份数据
        BalanceSheetRecord record = new BalanceSheetRecord();
        record.setContractNo(contract.getContractNo());
        record.setProjectId(contract.getProjectId());
        record.setRoomId(contract.getRoomId());
        record.setHouseNum(houseNum);
        record.setContractId(contract.getId());
        record.setBillId(billId);
        record.setBillNo(billTail.getDetailNo());
        record.setBillMonth(billMonth);
        record.setBillName(billName);
        record.setAmountReceived(0.00);
        record.setBillStatus(BillStatusEnum.BIST3.getCode());
        record.setBillType(LandeaConstants.BillType.MONTHLY_BILL);
        record.setExpenditureCode(billTail.getType());
        String expenditure = balanceSheetRecordReadMapper.selectExpenditure(billTail.getType());
        record.setExpenditure(expenditure);
        record.setStartTime(billTail.getDateBegin());
        record.setReceivableMoney(billTail.getMoney());
        record.setEndTime(billTail.getDateEnd());
        record.setMark(billTail.getRemark());
        record.setProduceType(ProduceType.PAY);

        return record;
    }

    @Override
    public BillDetailAndPayDTO queryBillDetailAndPayList(Integer billId)
        throws LandseaBusinessException
    {
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        if (userId == null)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.TOKEN_IS_NULL);
        }
        if (billId == null)
        {
            throw new LandseaBusinessException(LandseaErrorMessageEnum.BILL_INFO_BILL_ID_IS_NULL);
        }
        BillDetailAndPayDTO billDetailAndPayDTO = new BillDetailAndPayDTO();
        billDetailAndPayDTO.setBillDetailList(billDetailReadMapper.selectDetailListById(billId));
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("billId", billId);
        map.put("billType", BTypeEnum._BT1.getCode());
        List<BillPaymentDTO> payList = billPaymentReadMapper.selectListByBillId(map);
        if (payList.size() > 0)
        {
            for (BillPaymentDTO dto : payList)
            {
                if (!StringUtils.isEmpty(dto.getImageId()))
                {
                    dto.setImagePath(fileService.getFilesByIds(dto.getImageId()));
                }
            }
        }
        billDetailAndPayDTO.setBillPaymentList(payList);
        return billDetailAndPayDTO;
    }

    @Override
    public void deleteNoPayBill(List<ContractNeedDelReq> list)
    {
        if (list != null && list.size() > 0)
        {
            billWriteMapper.deleteNoPayBill(list);
        }
    }

    @Override
    public void createOffRentBill(PreOffrentBill preBill, List<PreOffrentDetailBill> details)
        throws LandseaException
    {
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        Bill parBill = this.convertPreBillToBill(preBill);
        String billNo = parBill.getContractNo()
            + new DecimalFormat("00").format(billReadMapper.getMaxBillCode(parBill.getContractId()));
        parBill.setBillNo(billNo);
        parBill.setName(ConstantChar.BILLING_OF_OFFRENT_BILL);

        String balanceBillStatus = BillStatusEnum.BIST3.getCode();
        //跨项目换房标识 金额大于等于0直接变为已支付
        if (preBill.getRefundAmount().doubleValue() == 0.0)
        {
            parBill.setBillStatus(BillStatusEnum.BIST2.getCode());
            balanceBillStatus = BillStatusEnum.BIST2.getCode();
        }
        parBill.updateCommonInfo(userId);
        billWriteMapper.insertSelective(parBill);

        // 专为换房使用，将生成的A合同退租账单保存到线程变量中，因为事务中无法获取刚插入的
        CurrentThread.set(PrepaymentService.CHANGE_ROOM_A_BILL, parBill);

        List<BillDetail> billDetails = new ArrayList<>();
        Contract contract = contractReadMapper.selectByPrimaryKey(preBill.getContractId());
        Room room = roomReadMapper.selectByPrimaryKey(contract.getRoomId());
        Integer detailNum = 0;
        if (details != null)
        {
            for (PreOffrentDetailBill detail : details)
            {
                detailNum++;
                String detailNo = billNo + String.format("%02d", detailNum);
                BillDetail bdt = this.convertPreDetailToBillDetail(detail);
                bdt.setBillId(parBill.getId());
                bdt.setDetailNo(detailNo);
                bdt.setOverlistFlag(0);
                bdt.updateCommonInfo(userId);

                // 构造独立账单记录,入库
                BalanceSheetRecord balanceSheetRecord =
                    buildOffrentBalanceRecord(detail, contract, preBill, detailNo, balanceBillStatus);
                balanceSheetRecord.setHouseNum(room.getHouseNum());
                billIndependentService.insertBalanceSheet(balanceSheetRecord);

                billDetails.add(bdt);
            }
        }
        if (billDetails.size() > 0)
        {
            billDetailWriteMapper.insertList(billDetails);
        }

        // 处理余额
        prepaymentService.offRentclearAccount(contract.getUserId(), contract.getRoomId());
    }

    // 退租生成独立账单记录
    private BalanceSheetRecord buildOffrentBalanceRecord(PreOffrentDetailBill detail, Contract contract,
        PreOffrentBill preBill, String detailNo, String balanceBillStatus)
    {
        // 往balanceSheet表中新增退房记录
        BalanceSheetRecord balanceSheetRecord = new BalanceSheetRecord();
        balanceSheetRecord.setContractNo(preBill.getContractNo());
        balanceSheetRecord.setContractId(preBill.getContractId());
        balanceSheetRecord.setBillNo(detailNo);
        balanceSheetRecord.setProjectId(contract.getProjectId());
        balanceSheetRecord.setRoomId(contract.getRoomId());
        balanceSheetRecord.setBillId(preBill.getId());
        balanceSheetRecord.setBillMonth(preBill.getBillMonth());
        balanceSheetRecord.setBillName(ConstantChar.BILLING_OF_OFFRENT_BILL);

        if (BillStatusEnum.BIST2.getCode().equals(balanceBillStatus))
        {
            balanceSheetRecord.setAmountReceived(detail.getBillAmount());
            balanceSheetRecord.setPayTime(new Date());
        }
        balanceSheetRecord.setBillStatus(balanceBillStatus);
        balanceSheetRecord.setBillType(LandeaConstants.BillType.MONTHLY_BILL);

        balanceSheetRecord.setExpenditureCode(detail.getPreType());
        String expenditure = balanceSheetRecordReadMapper.selectExpenditure(detail.getPreType());
        balanceSheetRecord.setStartTime(detail.getDateBegin());
        balanceSheetRecord.setReceivableMoney(detail.getBillAmount());
        balanceSheetRecord.setEndTime(detail.getDateEnd());
        balanceSheetRecord.setMark(preBill.getComment());

        // 如果是授权码
        if (detail.getCouponFlag())
        {
            balanceSheetRecord
                .setExpenditure(billIndependentService.getAuthCodeBillType(detail.getPreType(), expenditure));
            balanceSheetRecord.setProduceType(ProduceType.AUTHCODE);
        }
        else
        {
            balanceSheetRecord.setExpenditure(expenditure);
            balanceSheetRecord.setProduceType(ProduceType.PAY);
        }

        return balanceSheetRecord;
    }

    public Bill convertPreBillToBill(PreOffrentBill preBill)
    {
        Bill bill = new Bill();
        bill.setContractId(preBill.getContractId());
        bill.setContractNo(preBill.getContractNo() == null ? null : preBill.getContractNo());
        bill.setReceivableMoney(preBill.getRefundAmount());
        bill.setAmountReceived(0.0);
        bill.setBillStatus(BillStatusEnum.BIST3.getCode());
        bill.setBillMonth(preBill.getBillMonth());
        return bill;
    }

    @Override
    public BillDetail convertPreDetailToBillDetail(PreOffrentDetailBill preDtBill)
    {
        BillDetail billDt = new BillDetail();
        billDt.setDateBegin(preDtBill.getDateBegin());
        billDt.setDateEnd(preDtBill.getDateEnd());

        // 如果有余额,费项为其他,在备注中增加说明
        if (BillTypeEnum.CTBI22.getCode().equals(preDtBill.getPreType()))
        {
            billDt.setType(BillTypeEnum.CTBI22.getCode());
            billDt.setRemark("账户余额清退");
        }
        else
        {
            billDt.setType(preDtBill.getPreType());
            billDt.setRemark(preDtBill.getComments());
        }
        billDt.setMoney(preDtBill.getBillAmount());
        billDt.setCouponFlag(preDtBill.getCouponFlag());
        billDt.setCouponId(preDtBill.getId());
        billDt.setAuthCode(preDtBill.getAuthCode());
        return billDt;
    }

    /**
     * 强行退房 退款/收款后账单操作
     *
     * @param refund 退款记录
     * @throws LandseaException
     */
    @Override
    public void billCuteAfterConfirm(Refund refund)
        throws LandseaException
    {
        // 如果是强行退房
        // 强行退房 退不出去的->账单新增一条子项 ：营业外收入 1000元
        // 强行退房 收不回来的->账单新增一条子项 ：其他应收款 -1000元
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        Date currentTime = new Date();
        if (refund.getRefundType().equals(RefundTypeEnum._RFDTYPE3.getCode()))
        {// 强行退房
         // 获取退房账单
            Bill offBill = billReadMapper.queryLastBill(refund.getObjectId());
            if (offBill != null)
            {
                if (offBill.getName().equals(ConstantChar.BILLING_OF_OFFRENT_BILL))
                {
                    List<BillDetailDTO> bdList = billDetailReadMapper.selectDetailListById(offBill.getId());
                    BillDetail billDetail = new BillDetail();
                    billDetail.setBillId(offBill.getId());
                    billDetail.setDetailNo(offBill.getBillNo() + String.format("%02d", bdList.size() + 1));
                    billDetail.setDateBegin(currentTime);
                    billDetail.setDateEnd(currentTime);
                    if (refund.getRefundAmount() < 0)
                    { // 给租客钱
                        billDetail.setType(BillTypeEnum.CTBI15.getCode());
                        billDetail.setMoney(LandSeaUtil.get2Double(Math.abs(refund.getRefundAmount())));
                    }
                    else
                    {// 收租客钱
                        billDetail.setType(BillTypeEnum.CTBI16.getCode());
                        billDetail.setMoney(LandSeaUtil.get2Double(-(refund.getRefundAmount())));
                    }
                    billDetail.updateCommonInfo(userId);
                    billDetailWriteMapper.insertSelective(billDetail);

                    // 往balanceSheet中添加强行退房记录
                    BalanceSheetRecord balanceSheetRecord = buildRefundBalanceRecord(offBill, billDetail);
                    billIndependentService.insertBalanceSheet(balanceSheetRecord);

                    // 修改preBill
                    offBill.setAmountReceived(LandSeaUtil.get2Double(offBill.getReceivableMoney()));
                    // offBill.setReceivableMoney(0.0);
                    offBill.setBillStatus(BillStatusEnum.BIST2.getCode());
                    offBill.updateCommonInfo(userId);
                    billWriteMapper.updateByPrimaryKeySelective(offBill);
                    // 修改
                    billIndependentService.monUpdateBalanceSheet(offBill.getId(), currentTime);
                }
            }
        }
        else if (refund.getRefundType().equals(RefundTypeEnum._RFDTYPE2.getCode()))
        { // 正常退房
          // 获取退房账单 修改账单为已支付/实收=退款金额
            Bill offBill = billReadMapper.queryLastBill(refund.getObjectId());
            if (offBill != null)
            {
                if (offBill.getName().equals(ConstantChar.BILLING_OF_OFFRENT_BILL))
                {
                    // 修改preBill
                    offBill.setAmountReceived(LandSeaUtil.get2Double(refund.getRefundAmount()));
                    offBill.setBillStatus(BillStatusEnum.BIST2.getCode());
                    offBill.updateCommonInfo(userId);
                    billWriteMapper.updateByPrimaryKeySelective(offBill);
                    // 往balanceSheet中做修改
                    billIndependentService.monUpdateBalanceSheet(offBill.getId(), currentTime);
                }
            }
            // 独立账单处理
        }
        else if (refund.getRefundType().equals(RefundTypeEnum._RFDTYPE1.getCode()))
        { // 定金处理
          // 预定状态改为->已退款
            Deposit deposit = depositReadMapper.selectByPrimaryKey(refund.getObjectId());
            deposit.setStatus(DepositEnum._DS5.getCode());
            deposit.updateCommonInfo(SecurityUtils.getCurrentLogin().getUserId());
            deposit.setId(refund.getObjectId());
            depositWriteMapper.updateByPrimaryKeySelective(deposit);

            // 退款金额
            Double offAmount = refund.getRefundAmount();

            // 预定独立账单处理
            BalanceSheetRecord balanceSheetRecord = buildbackMoneyBalanceRecord(deposit, offAmount);
            billIndependentService.insertBalanceSheet(balanceSheetRecord);
        }
    }

    // 强行退房入库独立账单
    private BalanceSheetRecord buildRefundBalanceRecord(Bill offBill, BillDetail billDetail)
    {
        BalanceSheetRecord record = new BalanceSheetRecord();

        record.setBillNo(offBill.getBillNo());
        Contract contract = contractReadMapper.selectByPrimaryKey(offBill.getContractId());
        record.setProjectId(contract.getProjectId());
        record.setContractNo(contract.getContractNo());
        record.setRoomId(contract.getRoomId());
        record.setContractId(contract.getId());
        Room room = roomReadMapper.selectByPrimaryKey(contract.getRoomId());

        record.setHouseNum(room.getHouseNum());

        record.setBillId(offBill.getId());
        record.setBillMonth(offBill.getBillMonth());
        record.setBillName(ConstantChar.BILLING_OF_OFFRENT_BILL);
        record.setAmountReceived(0.00);
        record.setBillStatus(BillStatusEnum.BIST3.getCode());
        record.setBillType(LandeaConstants.BillType.MONTHLY_BILL);

        record.setExpenditureCode(billDetail.getType());
        String expenditure = balanceSheetRecordReadMapper.selectExpenditure(billDetail.getType());
        record.setExpenditure(expenditure);
        record.setStartTime(billDetail.getDateBegin());
        record.setReceivableMoney(billDetail.getMoney());
        record.setEndTime(billDetail.getDateEnd());
        record.setMark(billDetail.getRemark());
        record.setProduceType(ProduceType.GOBACK);

        return record;
    }

    // 退租定金退还入独立账单
    private BalanceSheetRecord buildbackMoneyBalanceRecord(Deposit deposit, Double offAmount)
    {
        // 预定独立账单处理
        // 往balanceSheet中存入定金退款
        BalanceSheetRecord record = new BalanceSheetRecord();
        record.setProjectId(deposit.getPrjId());
        record.setRoomId(deposit.getRoomId());
        Room room = roomReadMapper.selectByPrimaryKey(deposit.getRoomId());

        record.setHouseNum(room.getHouseNum());

        record.setBillId(deposit.getId());

        record.setAmountReceived(-offAmount);
        record.setBillStatus(BillStatusEnum.BIST2.getCode());
        record.setBillType(LandeaConstants.BillType.RESERVE_BILL);

        record.setExpenditureCode(BillTypeEnum.CTBI17.getCode());
        String expenditure = balanceSheetRecordReadMapper.selectExpenditure(BillTypeEnum.CTBI17.getCode());
        record.setExpenditure(expenditure);
        record.setStartTime(new Date());
        record.setReceivableMoney(-offAmount);
        record.setEndTime(new Date());
        record.setProduceType(ProduceType.GOBACK);

        return record;
    }

    @Override
    public void delayOffBillSendMsg()
        throws LandseaException
    {
        // 重新未处理的账单以及所属项目的管家信息
        // 退房账单未处理的时间
        Integer days = configurationService.getIntegerValue("UNRENT_OFFRENT_BILL_DELAY_DAYS");
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("billName", ConstantChar.BILLING_OF_OFFRENT_BILL);
        map.put("billStatus", BillStatusEnum.BIST2.getCode());
        map.put("offrentDate", DateUtil.getInstence().convertDate(DateUtils.addDays(new Date(), -days)));
        List<DelayOffBillDTO> delayOffBills = billReadMapper.queryDelayOffBills(map);
        log.info("delayOffBillSendMsg|定时任务-退房账单超过N天不处理将发送短信给管家开始......");
        if (delayOffBills != null)
        {
            // 发送短信->合同编号${contractNo}，租客姓名${customerName}，退房日期${time}，该笔退房账单尚未处理，请保持跟进！
            for (DelayOffBillDTO dto : delayOffBills)
            {
                String offDate = DateUtil.getInstence().convertDate(dto.getCreatedDtm());

                String msg = " {'contractNo':'" + dto.getContractNo() + "','customerName':'" + dto.getCustomerName()
                    + "','time':'" + offDate + "'}";

                log.info("delayOffBillSendMsg|退房账单信息及推送管家信息:" + msg + ":" + dto.getWorkPhoneNumber());
                String signName = configurationService.getStringValue(ConfigurationService.SEND_MSG_SIGN_NAME);
                SMSUtil.sendSms(sendMsgConfiguration.getOffrentdelayMessageCode(), dto.getWorkPhoneNumber(), msg,signName);
            }
        }
    }

    @Override
    public void deleteNoPay(Integer contractId)
    {
        billWriteMapper.deleteNoPayForCId(contractId);
    }

    @Override
    public void createMonthBillByContractId(Integer contractId)
        throws LandseaException
    {
        String billMonth = DateUtil.getInstence().convertYM(new Date());
        List<ContractBillDTO> contractList = contractReadMapper.queryForCreateBill(billMonth);
        for (ContractBillDTO contract : contractList)
        {
            if (contractId.equals(contract.getId()))
            {
                Integer billId = createMonthBills(contract, billMonth);
                // 需要推送消息Tony
                // sendMsgServiceImpl.sendMessage(contract.getId(),BTypeEnum._BT1.getCode(),billId);
            }

        }
    }

    @Override
    public Bill queryFirstBill(Integer contractId)
        throws LandseaException
    {
        return billReadMapper.queryFirstBill(contractId);
    }

    @Override
    public void sendMsgByMonthBill(HashMap map)
        throws LandseaException
    {
        List<BillDTO> billDTOs = billReadMapper.selectBillBySendMsg(map);
        if (billDTOs != null)
        {
            for (BillDTO billDTO : billDTOs)
            {
                if (billDTO.getReceivableMoney() != 0.0)
                {
                    log.info("Send MonthBill Msg,Bill Id is:" + billDTO.getId());
                    sendMsgServiceImpl.sendMessage(billDTO.getContractId(), BTypeEnum._BT1.getCode(), billDTO.getId());
                }
            }
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {LandseaException.class, BusinessException.class,
        Exception.class})
    public void addAuthCode(Object object)
        throws LandseaException, BusinessException, Exception
    {
        AddAuthCodeReq authCode = (AddAuthCodeReq)object;

        if (authCode.getContractId() == null || authCode.getBillId() == null)
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.AUTHCODE_USED_CIDPID_ERROR);
        }
        Contract contract = contractReadMapper.selectByPrimaryKey(authCode.getContractId());
        if (contract == null)
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.AUTHCODE_USED_CIDPID_ERROR);
        }
        else
        {
            if (contract.getDeleted())
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.AUTHCODE_USED_CIDPID_ERROR);
            }
        }
        Bill bill = billReadMapper.selectKey(authCode.getBillId());
        if (bill == null)
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.AUTHCODE_USED_CIDPID_ERROR);
        }
        // 未支付或者部分支付但全是余额抵扣的才可以使用授权码
        String billStatus = bill.getBillStatus();
        if (!(billStatus.equals(BillStatusEnum.BIST3.getCode())
            || (billStatus.equals(BillStatusEnum.BIST1.getCode()) && bill.getPayWithMoney().intValue() != 1)))
        {
            throw LandseaException.createException(LandseaErrorMessageEnum.AUTHCODE_USED_BILLSTATUS_ERROR);
        }
        // 获取账单中的原始金额
        List<BillDetail> billDetails = billDetailReadMapper.getBillDetail(bill.getId());
        Double orgAmount = 0.0;
        List<BillDetail> orgBillDetail =
            billDetails.stream().filter(e -> e.getCouponFlag() == false).collect(Collectors.toList());
        if (orgBillDetail.size() > 0)
        {
            for (BillDetail bd : orgBillDetail)
            {
                orgAmount = orgAmount + bd.getMoney();
            }
        }
        // 先删除之前用过的授权码
        couponUsedService.removeCouponUesd(bill.getId(), LandeaConstants.QueryUesdCodeType.MONTHBILL);
        // 删除账单中的优惠类账单
        billDetailWriteMapper.removeAuthCodeBill(bill.getId());
        // 删除独立账单中授权码
        Map<String, Object> deleteBalanceMap = new HashMap<String, Object>();
        deleteBalanceMap.put("billId", bill.getId());
        deleteBalanceMap.put("billMonth", bill.getBillMonth());
        deleteBalanceMap.put("billType", BillType.MONTHLY_BILL);
        billIndependentService.deleteCouponRecord(deleteBalanceMap);
        if (!CollectionUtils.isEmpty(authCode.getAuthCodes()))
        {
            // 校验是否能使用
            couponUsedService.checkUesdAuthCode(LandeaConstants.QueryUesdCodeType.MONTHBILL,
                contract.getUserId(),
                authCode.getAuthCodes());
            // 新增使用记录
            AddCouponUesd addCodes = new AddCouponUesd();
            addCodes.setUserId(contract.getUserId());
            addCodes.setRoomId(contract.getRoomId());
            addCodes.setAuthCodes(authCode.getAuthCodes());
            addCodes.setPrjId(contract.getProjectId());
            addCodes.setMainId(bill.getId());
            addCodes.setMainType(LandeaConstants.QueryUesdCodeType.MONTHBILL);
            couponUsedService.insertUesdList(addCodes);
            // 查询费项
            List<BillDetail> detailBills = billDetailReadMapper.getBillDetail(authCode.getBillId());
            if (detailBills == null)
            {
                throw LandseaException.createException(LandseaErrorMessageEnum.BILLPAYMENT_QUERY_BILL_ERROR);
            }
            detailBills = detailBills.stream().filter(e -> e.getCouponFlag() == false).collect(Collectors.toList());
            // 校验授权码金额是否正确
            List<UsedAuthCode> moneyCodes = authCode.getAuthCodes()
                .stream()
                .filter(e -> e.getOfferType().equals("BS") && e.getMoneyType().equals(LandeaConstants.MoneyType.MONEY))
                .collect(Collectors.toList());
            if (moneyCodes != null)
            {
                Set<String> authTypeSet = new HashSet<>();
                moneyCodes.forEach(e -> authTypeSet.add(e.getAuthFee()));
                for (String authType : authTypeSet)
                {
                    // 获取费项此次费项应缴金额
                    Optional<BillDetail> bd =
                        detailBills.stream().filter(e -> e.getType().equals(authType)).findFirst();
                    if (bd.isPresent())
                    {
                        Double feeAmout = bd.get().getMoney();
                        // 获取此次授权码当前类型减免金额
                        List<UsedAuthCode> feeAuthCodes = moneyCodes.stream()
                            .filter(e -> e.getAuthFee().equals(authType))
                            .collect(Collectors.toList());
                        Double authTypeAmout = 0.0;
                        if (feeAuthCodes != null)
                        {
                            for (UsedAuthCode uac : feeAuthCodes)
                            {
                                authTypeAmout = authTypeAmout + uac.getQuota();
                            }
                        }
                        if (authTypeAmout.compareTo(feeAmout) > 0)
                        {
                            throw new BusinessException(
                                String.format(LandseaErrorMessageEnum.AUTHCODE_USED_AMOUT_ERROR.getDesc(), authType));
                        }
                    }
                }
            }
            // 新增授权码使用记录->账单
            List<BillDetail> records = new ArrayList<>();
            // 优惠金额
            Double totalAuthAmout = 0.0;
            
            int totalBillDetail = orgBillDetail.size();
            for (UsedAuthCode usedCode : authCode.getAuthCodes())
            {
                BillDetail detailBill = new BillDetail();
                detailBill.setBillId(bill.getId());
                detailBill.setType(usedCode.getAuthFee());
                Optional<BillDetail> dBill =
                    detailBills.stream().filter(e -> e.getType().equals(usedCode.getAuthFee())).findFirst();
                // 如果是折扣 则取相对于费项金额进行计算
                Double authAmout = 0.0;
                if (usedCode.getMoneyType().equals(LandeaConstants.MoneyType.DISCOUNT))
                {
                    if (dBill.isPresent())
                    {
                        authAmout = LandSeaUtil
                            .get2Double(dBill.get().getMoney() - (dBill.get().getMoney() * usedCode.getQuota()));
                    }
                }
                else
                {
                    authAmout = usedCode.getQuota();
                }
                detailBill.setMoney(-authAmout);
                if (dBill.isPresent())
                {
                    detailBill.setDateBegin(dBill.get().getDateBegin());
                    detailBill.setDateEnd(dBill.get().getDateEnd());
                }
                detailBill.setAuthCode(usedCode.getAuthCode());
                detailBill.setCouponId(usedCode.getId());
                detailBill.setCouponFlag(true);
                detailBill.setOverlistFlag(0);
                
                totalBillDetail ++;
                // 账单编号
                detailBill.setDetailNo(bill.getBillNo() + String.format("%02d", totalBillDetail));
                
                detailBill.updateCommonInfo(SecurityUtils.getCurrentLogin().getUserId());
                records.add(detailBill);
                totalAuthAmout = totalAuthAmout + authAmout;
            }
            billDetailWriteMapper.insertList(records);
            // 入独立账单
            billIndependentService.insertFromMonth(records, contract, bill);
            // 修改账单金额
            bill.setReceivableMoney(LandSeaUtil.get2Double(orgAmount - totalAuthAmout));
        }
        else
        {
            // 修改账单金额
            bill.setReceivableMoney(LandSeaUtil.get2Double(orgAmount));
        }
        bill.updateCommonInfo(SecurityUtils.getCurrentLogin().getUserId());
        billWriteMapper.updateByPrimaryKeySelective(bill);
    }

    @Override
    public void deleteBill(Integer billId)
    {
        billWriteMapper.deleteBillById(billId);
        billDetailWriteMapper.deleteByBillId(billId);
        billPaymentWriteMapper.deleteByBillId(billId);
    }

    /**
     *
     * @param contractId b合同
     * @param billId
     */
    @Override
    public void updateMontlyOrgContract(Integer contractId, Integer billId)
    {
        Contract contract = contractReadMapper.selectByPrimaryKey(contractId);
        BillDTO bill = billReadMapper.selectByPrimaryKey(billId);
        Integer userId = SecurityUtils.getCurrentLogin().getUserId();
        // 如果是续租合同则需处理A合同的其余费项
        if (contract.getOriginalCId() != null && contract.getRentType() != null)
        {
            if (contract.getRentType().equals(LandeaConstants.ReRentType.RERENT))
            {
                List<BillDetail> billDetails = billDetailReadMapper.getBillDetail(billId);
                if (billDetails.size() > 0)
                {
                    List<BillDetail> overListMonthly =
                        billDetails.stream().filter(e -> e.getOverlistFlag() == 1).collect(Collectors.toList());
                    if (overListMonthly.size() > 0)
                    {
                        for (BillDetail bd : overListMonthly)
                        {
                            MonthlyBill monthlyBill = new MonthlyBill();
                            monthlyBill.setStatus(BillStatusEnum.BIST2.getCode());
                            monthlyBill.setBillNo(bill.getBillNo());
                            monthlyBill.setContractId(contract.getOriginalCId());
                            monthlyBill.setExpenseType(bd.getType());
                            monthlyBill.setBillPrice(bd.getMoney());
                            monthlyBill.setStartTime(bd.getDateBegin());
                            monthlyBill.setEndTime(bd.getDateEnd());
                            monthlyBill.updateCommonInfo(userId);
                            monthlyBillWriteMapper.updateByMBill(monthlyBill);
                            log.info("first bill payment Original Contract:" + contract.getOriginalCId()
                                + " Monthly Bill type is pay :" + bd.getType());
                        }
                    }
                }
            }
/*            else if (LandeaConstants.ReRentType.TURN.equals(contract.getRentType()))
            {
                try
                {
                    // 如果是换房，B合同支付成功后，如果A账户有余额，要转到B
                    // 虽然在B合同签字时做过一次，但有可能失败，所以在此处仍然做一次确保
                    // 如果发生错误，只记录日志
                    prepaymentService.changeRoomAccountProcess(contract, contract.getOriginalCId());
                }
                catch (LandseaException e)
                {
                    log.error("换房A账户结转到B账户发生错误." + e.getErrorMsg());
                }
            }
*/        }
    }

    @Override
    public Bill queryLastBill(Integer contractId)
    {
        return billReadMapper.queryLastBill(contractId);
    }

    private ContractBillDTO contract2ContractBillDTO(Contract contract)
    {
        ContractBillDTO result = new ContractBillDTO();
        result.setBillStatus(contract.getBillStatus());
        result.setContractNo(contract.getContractNo());
        result.setDeposit(contract.getDeposit());
        result.setDepositId(contract.getDepositId());
        result.setDepositM(contract.getDepositM());
        result.setDiscount(contract.getDiscount());
        result.setEnterprise(contract.getEnterprise());
        result.setId(contract.getId());
        result.setInDate(contract.getInDate());
        result.setLease(contract.getLease());
        result.setLeaseD(contract.getLeaseD());
        result.setLeaseM(contract.getLeaseM());
        result.setOriginalCId(contract.getOriginalCId());
        result.setOriginalPrice(contract.getOriginalPrice());
        result.setOutDate(contract.getOutDate());
        result.setPayM(contract.getPayM());
        result.setPaymentMode(contract.getPaymentMode());
        result.setPayTime(contract.getPayTime());
        result.setProjectId(contract.getProjectId());
        result.setPropertyFee(contract.getPropertyFee());
        result.setRemark(contract.getRemark());
        result.setRentStatus(contract.getRentStatus());
        result.setRentType(contract.getRentType());
        result.setRoomId(contract.getRoomId());
        result.setSalePrice(contract.getSalePrice());
        result.setSignDate(contract.getSignDate());
        result.setSource(contract.getSource());
        result.setStatus(contract.getStatus());
        result.setUserId(contract.getUserId());
        result.setWaterDeposit(contract.getWaterDeposit());

        return result;
    }


    @Override
    public String checkBillPay(Integer contractId) throws LandseaException {
        StringBuffer checkFlag = new StringBuffer();
        HashMap queryMap = new HashMap();
        queryMap.put("contractId",contractId);
        queryMap.put("billStatus",BillStatusEnum.BIST1);
        List<BillDTO> halfPayBills = billReadMapper.selectAllByCurrent(queryMap);
        queryMap.put("billStatus",BillStatusEnum.BIST3);
        List<BillDTO> noPayBills = billReadMapper.selectAllByCurrent(queryMap);
        List<RealBill> noPayRBills = realBillReadMapper.getByContractAdStatus(queryMap);
        checkFlag.append(halfPayBills.size()).append("|").append(noPayBills.size()).append("|").append(noPayRBills.size());
        return checkFlag.toString();

    }

    @Override
    public void createMonthBillByCIds(String ids) throws LandseaException {
        if(!StringUtils.isEmpty(ids)){
            List<String> cids = java.util.Arrays.asList(ids.split(","));
            String billMonth = DateUtil.getInstence().convertYM(new Date());
            HashMap<String,Object> queryMap = new HashMap<>();
            queryMap.put("billDate",billMonth);
            queryMap.put("ids",cids);
            List<ContractBillDTO> contractList = contractReadMapper.queryByIdsForCreateBill(queryMap);
            for (ContractBillDTO contract : contractList)
            {
                if (contract.getLeaseM() == null || contract.getDepositM() == null || contract.getPayM() == null)
                {
                    log.info("contract id" + contract.getId() + " createMonthBill failed "
                            + LandseaErrorMessageEnum.BILL_LEASEINFO_IS_NULL.getDesc());
                    continue;
                }
                String endMonth =DateUtil.getInstence().convertYM(contract.getOutDate());
                //续租
                String rentType = contract.getRentStatus()==null ? "": contract.getRentType();
                //如果是续租A合同 最后则不在出账单
                if(contract.getOriginalCId()==null && rentType.equals(LandeaConstants.ReRentType.RERENT) &&  endMonth.equals(billMonth)){
                    log.info("contract id" + contract.getId() + " contractA no createBill");
                    continue;
                }
                try{
                    createMonthBills(contract, billMonth);
                    // 余额自动抵扣账单,签字的合同才会自动抵扣
                    if (ContractStatusCodeEnum.SIGN.getCode().equals(contract.getStatus())){
                        autoPayBillService.autoPay4CreateMonthBill(contract.getUserId(), contract.getRoomId());
                    }
                }catch (Exception e){
                    log.info("create bill error ,contract id is " + contract.getId());
                    log.error("create bill error msg is : "+ e.getMessage());
                }
            }
        }
    }

    @Override
    public void changeBill(Integer id) throws LandseaException {
        //check bill状态
        BillDTO bill = billReadMapper.selectByPrimaryKey(id);
        Contract contract = contractReadMapper.selectByPrimaryKey(bill.getContractId());
        //账单状态未支付
        if(bill!=null && !bill.getBillStatus().equals(BillStatusEnum.BIST3.getCode())){
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_HAS_PAY_MONTHLYBILL);
        }
        //仅为月结账单
        if(!bill.getName().contains("第")){
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_NOT_MONTHLYBILL);
        }
        List<BillDetail> billDetails = billDetailReadMapper.getBillDetail(id);
        //不能存在房租、服务费授权码
        List<BillDetail> authBills = billDetails.stream().filter(e -> ((e.getType().equals(BillTypeEnum.CTBI8.getCode()) || e.getType().equals(BillTypeEnum.CTBI9.getCode()))
                && e.getCouponFlag())).collect(Collectors.toList());
        if (authBills!=null && authBills.size()>0) {
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_HAS_AUTH_MONTHLYBILL);
        }
        //不能存在滞纳金
        Optional<BillDetail> lateBill = billDetails.stream().filter(e -> e.getType().equals(BillTypeEnum.CTBI13.getCode())).findFirst();
        if(lateBill.isPresent()){
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_NO_LATE_MONTHLYBILL);
        }
        //获取上次房租服务费缴费日期
        Optional<BillDetail> billHourse = billDetails.stream().filter(e -> e.getType().equals(BillTypeEnum.CTBI8.getCode()) && !e.getCouponFlag()).findFirst();
        Optional<BillDetail> billProp = billDetails.stream().filter(e -> e.getType().equals(BillTypeEnum.CTBI9.getCode()) && !e.getCouponFlag()).findFirst();
        if (!billHourse.isPresent() || !billProp.isPresent()) {
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_NO_HOURSE_MONTHLYBILL);
        }
        //是否是最后一次出房租、服务费
        boolean lastHourseBill = false;
        int howManyMonthPay= contract.getPayM();
        //当期+付X>=租期+1 则认为是最后一期账单
        int leaseM = contract.getLeaseM()+1;
        Integer nowTerm = LandSeaUtil.getNumberFromStr(bill.getName());
        if(contract.getLeaseD()>0){
            leaseM = leaseM+1;
        }
        if(nowTerm+howManyMonthPay >= leaseM){
            lastHourseBill= true;
        }
        if(!lastHourseBill){
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_NOLAST_HOURSE_MONTHLYBILL);
        }
        //如果当期付款区间的不足pay m
        Date hourseStart = billHourse.get().getDateBegin();
        Date hourseEnd = billHourse.get().getDateEnd();
        int payM = DateUtil.getInstence().monthBetween(hourseStart,hourseEnd)+1;
        if(payM<=contract.getPayM()){
            throw LandseaException.createException(LandseaErrorMessageEnum.BILL_NOT_CHANGE);
        }
        updateBillInfo(bill, contract, billHourse, billProp);
    }

    private void updateBillInfo(BillDTO bill, Contract contract, Optional<BillDetail> billHourse, Optional<BillDetail> billProp) throws LandseaException {
        Integer systemId = SecurityUtils.getCurrentLogin().getUserId();
        //修改付租区间
        Date hourseStart = billHourse.get().getDateBegin();
        Date hourseEnd = billHourse.get().getDateEnd();
        int payM = DateUtil.getInstence().monthBetween(hourseStart,hourseEnd);
        Date changeEnd = DateUtil.getInstence().getLastDayOfLastMonth(hourseEnd);
        Double hoursePrice = contract.getSalePrice()*payM;
        Double propPrice = contract.getPropertyFee()*payM;
        BillDetail houseBill = new BillDetail();
        houseBill.setId(billHourse.get().getId());
        houseBill.setDateEnd(changeEnd);
        houseBill.setMoney(hoursePrice);
        houseBill.updateCommonInfo(systemId);
        billDetailWriteMapper.updateByPrimaryKeySelective(houseBill);
        BillDetail propBill = new BillDetail();
        propBill.setId(billProp.get().getId());
        propBill.setDateEnd(changeEnd);
        propBill.setMoney(propPrice);
        propBill.updateCommonInfo(systemId);
        billDetailWriteMapper.updateByPrimaryKeySelective(propBill);
        //计算差价
        Double houseFee = LandSeaUtil.get2Double(billHourse.get().getMoney()-hoursePrice);
        Double propFee = LandSeaUtil.get2Double(billProp.get().getMoney()- propPrice);
        Double totalPrice =  LandSeaUtil.get2Double(bill.getReceivableMoney()-houseFee-propFee);
        Bill updateBill = new Bill();
        updateBill.setId(bill.getId());
        updateBill.setReceivableMoney(totalPrice);
        updateBill.updateCommonInfo(systemId);
        updateBill.setBillMoney(totalPrice);
        billWriteMapper.updateByPrimaryKeySelective(updateBill);
        //修改独立账单
        BalanceSheetRecord houseBs = new BalanceSheetRecord();
        houseBs.setBillType(BTypeEnum._BT2.getCode());
        houseBs.setBillId(bill.getId());
        houseBs.setExpenditureCode(BillTypeEnum.CTBI8.getCode());
        houseBs.setReceivableMoney(hoursePrice);
        houseBs.setBillNo(billHourse.get().getDetailNo());
        houseBs.setEndTime(changeEnd);
        houseBs.setLastModifiedBy(systemId);
        houseBs.setLastModifiedDtm(new Date());
        balanceSheetRecordWriteMapper.updateMoneyByBillInfo(houseBs);
        BalanceSheetRecord propBs = new BalanceSheetRecord();
        propBs.setBillType(BTypeEnum._BT2.getCode());
        propBs.setBillId(bill.getId());
        propBs.setExpenditureCode(BillTypeEnum.CTBI9.getCode());
        propBs.setReceivableMoney(propPrice);
        propBs.setBillNo(billProp.get().getDetailNo());
        propBs.setLastModifiedBy(systemId);
        propBs.setEndTime(changeEnd);
        propBs.setLastModifiedDtm(new Date());
        balanceSheetRecordWriteMapper.updateMoneyByBillInfo(propBs);

        //修改合同增加拆分标识
        Contract updateCtr = new Contract();
        updateCtr.setId(contract.getId());
        updateCtr.setBillChange(true);
        updateCtr.updateCommonInfo(systemId);
        contractWriteMapper.updateByPrimaryKeySelective(updateCtr);
    }
}
